package com.itmatter.classifier.directdb;

import com.enigmastation.classifier.ClassifierException;
import com.enigmastation.classifier.ClassifierProbability;

import java.util.Arrays;
import java.util.Map;
import java.util.Set;

import com.itmatter.classifier.DBNaiveClassifier;
import java.util.HashMap;
import org.apache.log4j.Logger;

/**
 * Based on initial work from ci-bayes-2.1.0-SNAPSHOT-sources
 * @author Daniel Fisla, dfisla@itmatter.com, ITMATTER Inc., http://www.itmatter.com, Copyright 2010, LGPLv3
 */
public class DBNaiveClassifierImpl extends DBClassifierImpl implements DBNaiveClassifier {

    private static final Logger logger = Logger.getLogger(DBNaiveClassifierImpl.class.getName());
    protected Map<String, Double> thresholds = new HashMap<String, Double>();

    /**
     * 
     * @param cat
     * @param t
     */
    public void setCategoryThreshold(String cat, double t) {
        thresholds.put(cat, t);
    }

    /**
     * 
     * @param cat
     * @return
     */
    public double getCategoryThreshold(String cat) {
        
        if (thresholds.containsKey(cat)) {
            logger.debug("CAT-THRESHOLD-KEY: " + cat + " VAL: " + thresholds.get(cat));
            return thresholds.get(cat);
        } else {
            logger.debug("CAT-THRESHOLD-KEY: " + cat + " VAL-MISSING: " + 1.0);
            return 1.0;
        }
    }

    /**
     * 
     * @param item
     * @param defaultCat
     * @return
     */
    protected String classify(String item, String defaultCat) {
        return getClassification(item, defaultCat);
    }

    /**
     * 
     * @param item
     * @return
     */
    public ClassifierProbability[] getProbabilities(final Object item) {
        ClassifierProbability[] probabilities = new ClassifierProbability[getCategories().size()];
        Set<String> features = wordLister.getUniqueWords(item);

        int index = 0;
        for (String cat : getCategories()) {
            probabilities[index] = new ClassifierProbability();
            probabilities[index].setCategory(cat);
            probabilities[index].setScore(getProbabilityForCategory(features, cat));
            index++;
        }
        Arrays.sort(probabilities);
        return probabilities;
    }

    /**
     * 
     * @param probabilities
     */
    public void normalizeProbabilities(ClassifierProbability[] probabilities) {
        if (probabilities.length > 0) {
            double multiplier = 1.0 / probabilities[0].getScore();
            for (ClassifierProbability prob : probabilities) {
                prob.setScore(prob.getScore() * multiplier);
            }
        }
    }

    /**
     * 
     * @param item
     * @param defaultCat
     * @return
     */
    public String getClassification(Object item, String defaultCat) {
        if (getCategories().isEmpty()) {
            return defaultCat;
        }

        ClassifierProbability[] probs = getProbabilities(item);
        ClassifierProbability cp = probs[0];

        logger.debug("PROBS-COUNT: " + probs.length);
        
        for (ClassifierProbability p : probs) {
            if (p.getCategory().equals(cp.getCategory())) {
                continue;
            }
            if (p.getScore() * getCategoryThreshold(cp.getCategory()) > cp.getScore()) {
                return defaultCat;
            }
        }
        return cp.getCategory();
    }

    /**
     * This is an implementation that ignores thresholds. If there is no data in the classifier,
     * whooey! You get an exception. This resolves issue #2.
     *
     * @param item the item to be classified; needs to have meaningful toString()
     * @return a meaningful classification.
     */
    public String getClassification(Object item) {
        if (getCategories().isEmpty()) {
            throw new ClassifierException("No categories; please train before classification"
                    + " or provide default category.");
        }
        ClassifierProbability[] probs = getProbabilities(item);
        ClassifierProbability cp = probs[0];

        for (ClassifierProbability p : probs) {
            if (p.getCategory().equals(cp.getCategory())) {
                continue;
            }
            if (p.getScore() > cp.getScore()) {
                cp = p;
            }
        }
        return cp.getCategory();
    }

    /**
     * 
     * @param item
     * @param category
     * @return
     */
    protected double docprob(String item, String category) {
        return getDocumentProbabilityForCategory(item, category);
    }

    /**
     * 
     * @param item
     * @param category
     * @return
     */
    public double getDocumentProbabilityForCategory(Object item, String category) {
        Set<String> features = wordLister.getUniqueWords(item);
        return getDocumentProbabilityForCategory(features, category);
    }

    /**
     * Used to get combined probability for multiple features under hypothesis of belonging to a single category.
     * @param features
     * @param category
     * @return
     */
    public double getDocumentProbabilityForCategory(Set<String> features, String category) {
        //System.out.println("getDocumentProbabilityForCategory CAT: " + category);
        
        double p = 1.0;
        for (String f : features) {
            //System.out.println("getWeightedFeatureProbability FEA: " + f + " WPROB: " + getWeightedFeatureProbability(f, category));
            p *= getWeightedFeatureProbability(f, category);
        }
        return p;
    }

    /**
     * 
     * @param item
     * @param category
     * @return
     */
    public double getProbabilityForCategory(Object item, String category) {
        //System.out.println("CAT: " + category);
        
        double catprob = getCategoryCount(category);
        
        //System.out.println("CAT-COUNT: " + catprob);
        //System.out.println("TOTAL-COUNT: " + getTotalCount());
        catprob /= getTotalCount();
        //System.out.println("CAT-PROB: " + catprob);
        
        double dp = getDocumentProbabilityForCategory(item, category);
        dp *= catprob;
        return dp;
    }
    /*
    protected Set<ClassificationListener> classificationListeners = Sets.newHashSet();

    public void addListener(ClassificationListener listener) {
        classificationListeners.add(listener);
    }
     * 
     */
}
